import {
  DependenciesDef,
  dependenciesMerge,
  installDeps,
  ProjectCreator,
  ProjectCreatorBasicOptions,
  ProjectCreatorBasicState,
  ProjectStructure,
} from 'create-ts-next';
import { readFileSync } from 'fs';
import { isAbsolute, join, basename } from 'path';
import { terminal } from 'terminal-kit';
import { useCloudBaseContext } from './cloudbase-context';
import { validateProjectName } from './utils';

/////////////////////////////////////////////////////////////////////////////////
// Dependencies
/////////////////////////////////////////////////////////////////////////////////
const Dependencies: Record<string, DependenciesDef> = {
  typescript: {
    dependencies: {
      '@cloudbase/node-sdk': '2.9.1',
      'tcb-toolkit': '1.0.4',
    },
  },
};

/////////////////////////////////////////////////////////////////////////////////
// TcbCreator
/////////////////////////////////////////////////////////////////////////////////
interface ScfCreatorOptions extends ProjectCreatorBasicOptions {
  dir?: string;
}

interface ScfCreatorState extends ProjectCreatorBasicState {
  //
}

export class ScfCreator extends ProjectCreator<ScfCreatorOptions, ScfCreatorState> {
  ctx: ReturnType<typeof useCloudBaseContext>;

  constructor(opts: ScfCreatorOptions) {
    super(opts, {});
    validateProjectName(this.options.name);
    this.ctx = useCloudBaseContext(this.workingPath);
  }

  async startUp(): Promise<this> {
    await this.detectPackageCmd();
    const { name } = this.options;
    terminal(`Create SCF project `).cyan(name);
    process.stdout.write('\n');

    if (this.packageCmd != null) {
      terminal('Packages manager used: ').cyan(this.packageCmd);
      process.stdout.write('\n');
    }

    process.stdout.write('\n');
    this.create(this.getStructure());
    process.stdout.write('\n');

    terminal('Project ').cyan(name);
    terminal(' created. ');
    process.stdout.write('\n');
    process.stdout.write('\n');

    let installed = false;
    if (this.options.install) {
      if (this.packageCmd != null) {
        terminal('Install dependencies by ').cyan(this.packageCmd);
        terminal('... \n');
        try {
          await installDeps(this.workingPath, this.packageCmd);
          installed = true;
        } catch (e) {
          //
        }
      }

      process.stdout.write('\n');

      if (this.packageCmd != null && installed) {
        terminal.green('All dependencies has been installed!\n\n');
      }
    }

    ////////////////
    this.ctx.addScfProject(this.projectRoot, true);
    this.ctx.addDeployScript(this.scfBasename, true);
    ////////////////

    // entry the dir
    terminal('You can try the follow commands:\n');
    terminal.blue(`cd ${name}`);
    process.stdout.write('\n');

    const cmdRun =
      this.options.packageManager === 'pnpm' ? this.options.packageManager : `${this.options.packageManager} run`;

    if (!installed) {
      terminal.blue(`${this.options.packageManager} install`);
      process.stdout.write('\n');
    }

    terminal.blue(`${cmdRun} dev`);

    process.stdout.write('\n');
    process.stdout.write('\n');
    return this;
  }

  getStructure(): ProjectStructure {
    return {
      name: '',
      type: 'dir',
      children: [
        // dirs
        // files
        { name: 'package.json', data: this.generatePackageData() },
        { name: 'index.ts', data: this.generateIndexData() },
      ],
    };
  }

  generatePackageData(): string {
    const pkgData = {
      name: this.options.name,
      version: '1.0.0',
      description: `scf project ${this.options.name}`,
      scripts: {},
      author: '',
      license: 'UNLICENSED',
      private: true,
      ...(this.getDeps() || {}),
      scf: {
        ...this.ctx.mergeScfConfig(
          {
            timeout: 5,
            memorySize: 128,
            runtime: 'Nodejs16.13',
            handler: 'index.main',
            installDependency: true,
            ignore: ['node_modules', 'node_modules/**/*', '*.md', '*.ts', '*.yaml'],
          },
          this.ctx.pkg?.scf,
        ),
        name: this.scfBasename,
      },
    };
    return JSON.stringify(pkgData, null, 2);
  }

  generateIndexData(): string {
    if (this.scfBasename.startsWith('api-')) {
      return readFileSync(this.getTemplatePath('index.api.ts')).toString();
    }
    return readFileSync(this.getTemplatePath('index.ts')).toString();
  }

  getDeps(): DependenciesDef | undefined {
    let deps: DependenciesDef | undefined = undefined;
    Object.values(Dependencies).forEach(lib => {
      deps = dependenciesMerge(deps, lib);
    });
    return deps;
  }

  getTemplatePath(relative: string): string {
    return join(__dirname, `../template/scf/${relative}.ejs`);
  }

  get projectRoot(): string {
    return isAbsolute(this.options.name) ? this.options.name : join(this.ctx.functionRoot, this.scfBasename);
  }

  get isSpecifyDir(): boolean {
    return this.options.dir != null && this.options.dir.trim() === '';
  }

  get scfBasename(): string {
    return basename(this.options.name);
  }

  get workingPath(): string {
    const { dir } = this.options;
    if (dir == null || dir.trim() === '') {
      return this.basePath;
    }
    if (isAbsolute(dir)) {
      return dir;
    }
    return join(this.basePath, dir);
  }
}
